<template>
	<div ref="root" :style="rootStyle">
		<div :class="bem({ fixed: state.fixed && !isReset.value })" :style="stickyStyle">
			<slot></slot>
		</div>
	</div>
</template>
<script setup name="leisure-uni-sticky">
	import {
		ref,
		watch,
		computed,
		nextTick,
		reactive,
		defineComponent,
	} from 'vue';
	import {
		extend
	} from "../../libs/utils/basic.js";
	import {
		getZIndexStyle,
		unitToPx
	} from "../../libs/utils/format.js";
	import {
		onMountedOrActivated
	} from '../../libs/utils/onMountedOrActivated.js';
	import {
		numericProp,
		makeStringProp,
		makeNumericProp
	} from '../../libs/utils/props.js';
	import {
		createBEM,
		createNamespace
	} from '../../libs/utils/create.js';
	import {
		useEventListener
	} from '../../libs/utils/useEventListener.js';
	import {
		useRect
	} from '../../libs/utils/useRect.js';
	import {
		useScrollParent
	} from '../../libs/utils/useScrollParent.js';
	import {
		isHidden,
		getScrollTop,
		windowWidth,
		windowHeight,
	} from '../../libs/utils/dom.js';
	import {
		useVisibilityChange
	} from '../../libs/utils/use-visibility-change.js';


	const emit = defineEmits(['scroll', 'change']);

	const [name, bem] = createNamespace('sticky');

	// export type StickyPosition = 'top' | 'bottom';

	// 定义父组件传过来的值
	const props = defineProps({
		zIndex: {
			type: [Number, String],
			default: () => 0
		},
		position: {
			type: String,
			default: () => 'top'
		},
		container: {
			type: Object,
			default: null
		},
		offsetTop: {
			type: Number,
			default: () => 0
		},
		offsetBottom: {
			type: Number,
			default: () => 0
		},
	});

	const root = ref();
	const scrollParent = useScrollParent(root);
	const state = reactive({
		fixed: false,
		width: 0, // root width
		height: 0, // root height
		transform: 0,
	});

	const isReset = ref(false);
	const offset = computed(() =>
		unitToPx(props.position === 'top' ? props.offsetTop : props.offsetBottom),
	);

	const rootStyle = computed(() => {
		if (isReset.value) {
			return;
		}
		const {
			fixed,
			height,
			width
		} = state;
		if (fixed) {
			return {
				width: `${width}px`,
				height: `${height}px`,
			};
		}
	});
	const stickyStyle = computed(() => {
		if (!state.fixed || isReset.value) {
			return;
		}

		const style = extend(getZIndexStyle(props.zIndex), {
			width: `${state.width}px`,
			height: `${state.height}px`,
			[props.position]: `${offset.value}px`,
		});

		if (state.transform) {
			style.transform = `translate3d(0, ${state.transform}px, 0)`;
		}

		return style;
	});
	const emitScroll = (scrollTop) =>
		emit('scroll', {
			scrollTop,
			isFixed: state.fixed,
		});

	const onScroll = () => {
		if (!root.value || isHidden(root)) {
			return;
		}

		const {
			container,
			position
		} = props;
		const rootRect = useRect(root);
		const scrollTop = getScrollTop(window);

		state.width = rootRect.width;
		state.height = rootRect.height;

		if (position === 'top') {
			// The sticky component should be kept inside the container element
			if (container) {
				const containerRect = useRect(container);
				const difference = containerRect.bottom - offset.value - state.height;
				state.fixed = offset.value > rootRect.top && containerRect.bottom > 0;
				state.transform = difference < 0 ? difference : 0;
			} else {
				state.fixed = offset.value > rootRect.top;
			}
		} else {
			const {
				clientHeight
			} = document.documentElement;
			if (container) {
				const containerRect = useRect(container);
				const difference =
					clientHeight - containerRect.top - offset.value - state.height;
				state.fixed =
					clientHeight - offset.value < rootRect.bottom &&
					clientHeight > containerRect.top;
				state.transform = difference < 0 ? -difference : 0;
			} else {
				state.fixed = clientHeight - offset.value < rootRect.bottom;
			}
		}

		emitScroll(scrollTop);
	};

	watch(() => state.fixed, (value) => {
		emit('change', value)
	}, {
		immediate: true
	});

	useEventListener('scroll', onScroll, {
		target: scrollParent,
		passive: true,
	});
	useVisibilityChange(root, onScroll);

	watch([windowWidth, windowHeight], () => {
		if (!root.value || isHidden(root) || !state.fixed) {
			return;
		}
		isReset.value = true;
		nextTick(() => {
			const rootRect = useRect(root);
			state.width = rootRect.width;
			state.height = rootRect.height;
			isReset.value = false;
		});
	});
</script>

<style scoped lang="scss">
	:root,
	:host {
		--leisure-uni-sticky-z-index: 99;
	}

	.leisure-uni-sticky {
		&--fixed {
			position: fixed;
			z-index: var(--leisure-uni-sticky-z-index);
		}
	}
</style>